'''
 * CaRComp Execution Environment
 * 
 * Copyright (C) 2011 Taylor L. Riche
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
'''

import unittest
from carcomp.framework import application, component, connector, abstraction
from carcomp.framework.file_io import file_reader, file_writer
from carcomp.directory import localhost
import filecmp

class compA(component.Implementation):
    
    def __init__(self):
        component.Implementation.__init__(self, 'compA')
    
    def runComp(self, msg):
        #print('Made it to A: ' + msg)
        self.send_out('outPort', msg)
        
class compB(component.Implementation):
        
    def __init__(self):
        component.Implementation.__init__(self, 'compB')
    
    def runComp(self, msg):
        #print('Made it to B: ' + msg)
        self.send_out('outPort', msg)
        
class myApp(application.Application):
    
    def __init__(self):
        application.Application.__init__(self)
        self.comp_a = self.create_component(compA(), [('inPort', 'runComp'),('outPort', '__OUT__')])
        self.comp_b = self.create_component(compB(), [('inPort', 'runComp'),('outPort', '__OUT__')])
        conn = connector.Connector(connector.Endpoint(self.comp_a, self.comp_a.get_port('outPort')), 
                                     connector.Endpoint(self.comp_b, self.comp_b.get_port('inPort')))
        self.add_connection(conn)
        
        self.reader = file_reader.FileReaderComponent('{}ft/tests/framework/TEST_INPUT.txt'.format(localhost.local_directory.package_root))
        self.writer = file_writer.FileWriterComponent('{}ft/tests/framework/TEST_OUTPUT.txt'.format(localhost.local_directory.package_root))
        self.add_component(self.reader)
        self.add_component(self.writer)
        
        conn2 = connector.Connector(connector.Endpoint(self.comp_b, self.comp_b.get_port('outPort')),
                                      self.writer.get_input_endpoint())
        conn3 = connector.Connector(self.reader.get_output_endpoint(),
                                      connector.Endpoint(self.comp_a, self.comp_a.get_port('inPort')))
        
        self.add_connection(conn2)
        self.add_connection(conn3)
        
        self.threadA = application.CompThread(self.comp_a, [conn, conn3])
        self.threadB = application.CompThread(self.comp_b, [conn, conn2])
        self.threadC = application.CompThread(self.reader, [conn3], True)
        self.threadD = application.CompThread(self.writer, [conn2])
        
        self.add_threads([self.threadA, self.threadB, self.threadC, self.threadD])
        
class TestComp1(component.Implementation):
    
    def __init__(self, name):
        component.Implementation.__init__(self, name)
        self.add_port(component.Port('in', 'handler'))
        self.add_port(component.Port('out', '__OUT__'))
        
    def handler(self, msg):
        self.send_out('out', msg)
        
class TestComp2(component.Implementation):
    
    def __init__(self, name):
        component.Implementation.__init__(self, name)
        self.add_port(component.Port('in', 'handler'))
        self.add_port(component.Port('out', '__OUT__'))
        
    def handler(self, msg):
        #print('{}:{}'.format(self._name, msg))
        self.send_out('out', msg)
        
class AbsApp1(application.Application):
    
    def __init__(self):
        application.Application.__init__(self)
        abs = abstraction.Abstraction('test_abs')
        comp = TestComp1('test_comp')
        abs.add_component(comp)
        abs.add_port('out', (comp, 'out'), output=True)
        abs.add_port('in', (comp, 'in'))
        comp1 = TestComp1('test_comp_out1')
        comp2 = TestComp1('test_comp_out2')
        self.add_components([abs, comp1, comp2])
        c1_abs = connector.Connector(comp1.get_endpoint('out'), abs.get_endpoint('in'))
        abs_c2 = connector.Connector(abs.get_endpoint('out'), comp2.get_endpoint('in'))
        self.add_connections([c1_abs, abs_c2])
        
class AbsApp2(application.Application):
    
    def __init__(self):
        application.Application.__init__(self)
        abs = abstraction.Abstraction('test_abs')
        comp1 = TestComp1('test_comp_in1')
        abs.add_component(comp1)
        comp2 = TestComp1('test_comp_in2')
        abs.add_component(comp2)
        abs.add_port('out', (comp2, 'out'), output=True)
        abs.add_port('in', (comp1, 'in'))
        conn = connector.Connector(comp1.get_endpoint('out'), comp2.get_endpoint('in'))
        abs.add_connector(conn)
        comp1 = TestComp1('test_comp_out1')
        comp2 = TestComp1('test_comp_out2')
        self.add_components([abs, comp1, comp2])
        c1_abs = connector.Connector(comp1.get_endpoint('out'), abs.get_endpoint('in'))
        abs_c2 = connector.Connector(abs.get_endpoint('out'), comp2.get_endpoint('in'))
        self.add_connections([c1_abs, abs_c2])
        
class AbsApp3(application.Application):
    
    def __init__(self):
        application.Application.__init__(self)
        abs = abstraction.Abstraction('test_abs')
        comp = TestComp2('test_comp')
        abs.add_component(comp)
        abs.add_port('out', (comp, 'out'), output=True)
        abs.add_port('in', (comp, 'in'))
        abs2 = abstraction.Abstraction('test_shell_abs')
        abs2.add_component(abs)
        abs2.add_port('out', (abs, 'out'), output=True)
        abs2.add_port('in', (abs, 'in'))
        comp1 = TestComp2('test_comp_out1')
        comp2 = TestComp2('test_comp_out2')
        self.add_components([abs2, comp1, comp2])
        c1_abs = connector.Connector(comp1.get_endpoint('out'), abs2.get_endpoint('in'))
        abs_c2 = connector.Connector(abs2.get_endpoint('out'), comp2.get_endpoint('in'))
        self.add_connections([c1_abs, abs_c2])
        
class AbsApp4(application.Application):
    
    def __init__(self):
        application.Application.__init__(self)
        abs1 = abstraction.Abstraction('out_abs')
        comp1 = TestComp1('test_comp1')
        comp2 = TestComp1('test_comp2')
        abs2 = abstraction.Abstraction('in_abs')
        comp3 = TestComp2('test_comp3')
        abs1.add_component(comp1)
        abs1.add_component(comp2)
        abs1.add_component(abs2)
        abs1.add_port('in', (comp1, 'in'))
        abs1.add_port('out', (comp2, 'out'), output=True)
        abs2.add_component(comp3)
        abs2.add_port('in', (comp3, 'in'))
        abs2.add_port('out', (comp3, 'out'), output=True)
        c1_a2 = connector.Connector(comp1.get_endpoint('out'), abs2.get_endpoint('in'))
        a2_c2 = connector.Connector(abs2.get_endpoint('out'), comp2.get_endpoint('in'))
        abs1.add_connector(c1_a2)
        abs1.add_connector(a2_c2)
        self.add_component(abs1)

class Test(unittest.TestCase):


    def test_create_threads(self):
        '''Test automatic thread creation'''
        app = AbsApp1()
        fr = file_reader.FileReaderComponent('{}ft/tests/framework/TEST_INPUT.txt'.format(localhost.local_directory.package_root))
        fw = file_writer.FileWriterComponent('{}ft/tests/framework/DUMMY.txt'.format(localhost.local_directory.package_root))
        app.add_components([fr, fw])
        
        c2_fw = connector.Connector(app._components['test_comp_out2'].get_endpoint('out'),
                                    fw.get_input_endpoint())
        fr_c1 = connector.Connector(fr.get_output_endpoint(),
                                    app._components['test_comp_out1'].get_endpoint('in'))
        app.add_connections([fr_c1, c2_fw])
        app.resolve_abstractions()
        self.assertEqual(len(app._components), 5)
        self.assertEqual(len(app._connections), 4)
        app.create_threads()
        self.assertEqual(len(app._threads), 5)

    def test_application(self):
        '''Input and output files should be equal'''
        app = myApp()
        app.execute(True, 1)
        ret = filecmp.cmp('{}ft/tests/framework/TEST_INPUT.txt'.format(localhost.local_directory.package_root),
                          '{}ft/tests/framework/TEST_OUTPUT.txt'.format(localhost.local_directory.package_root),
                          False)
        self.assertEqual(ret, True, 'Files not equal')
        
    def test_abstraction_resolution_1(self):
        '''An application with one simple abstraction should end up with the correct sets of connectors/components.'''
        app = AbsApp1()
        app.resolve_abstractions()
        abs_list = [(name, comp) for name, comp in app._components.items() if isinstance(comp, abstraction.Abstraction)]
        self.assertEqual(0, len(abs_list))
        self.assertEqual(3, len(app._components), '{} components found, should be {}'.format(len(app._components), 3))
        self.assertEqual(2, len(app._connections), '{} connections found, should be {}'.format(len(app._connections), 2))
        
    def test_abstraction_resolution_2(self):
        '''An application with a 2-comp abstraction should end up with the correct sets of connectors/components.'''
        app = AbsApp2()
        app.resolve_abstractions()
        abs_list = [(name, comp) for name, comp in app._components.items() if isinstance(comp, abstraction.Abstraction)]
        self.assertEqual(0, len(abs_list))
        self.assertEqual(4, len(app._components), '{} components found, should be {}'.format(len(app._components), 4))
        self.assertEqual(3, len(app._connections), '{} connections found, should be {}'.format(len(app._connections), 3))
        
    def test_abstraction_resolution_3(self):
        '''An application with a 2-deep abstraction should end up with the correct sets of connectors/components.'''
        app = AbsApp3()
        app.resolve_abstractions()
        abs_list = [(name, comp) for name, comp in app._components.items() if isinstance(comp, abstraction.Abstraction)]
        self.assertEqual(0, len(abs_list))
        self.assertEqual(3, len(app._components), '{} components found, should be {}'.format(len(app._components), 3))
        self.assertEqual(2, len(app._connections), '{} connections found, should be {}'.format(len(app._connections), 2))
        
    def test_abstracation_resolution_4(self):
        '''An application that has several layers of nesting should be correctly resolved'''
        app = AbsApp4()
        app.resolve_abstractions()
        abs_list = [(name, comp) for name, comp in app._components.items() if isinstance(comp, abstraction.Abstraction)]
        self.assertEqual(0, len(abs_list))
        self.assertEqual(3, len(app._components), '{} components found, should be {}'.format(len(app._components), 3))
        self.assertEqual(2, len(app._connections), '{} connections found, should be {}'.format(len(app._connections), 2))
        
        
    def test_abs_res_exec_1(self):
        '''After resolving a simple abstraction, the app should execute correctly.'''
        app = AbsApp1()
        fr = file_reader.FileReaderComponent('{}ft/tests/framework/TEST_INPUT.txt'.format(localhost.local_directory.package_root))
        fw = file_writer.FileWriterComponent('{}ft/tests/framework/TEST_OUTPUT.txt'.format(localhost.local_directory.package_root))
        app.add_components([fr, fw])
        
        c2_fw = connector.Connector(app._components['test_comp_out2'].get_endpoint('out'),
                                    fw.get_input_endpoint())
        fr_c1 = connector.Connector(fr.get_output_endpoint(),
                                    app._components['test_comp_out1'].get_endpoint('in'))
        app.add_connections([fr_c1, c2_fw])
        app.resolve_abstractions()
        app.create_threads()
        
        app.execute(True, 1)
        
        ret = filecmp.cmp('{}ft/tests/framework/TEST_INPUT.txt'.format(localhost.local_directory.package_root),
                          '{}ft/tests/framework/TEST_OUTPUT.txt'.format(localhost.local_directory.package_root),
                          False)
        self.assertEqual(ret, True, 'Files not equal')
        
    def test_abs_res_exec_2(self):
        '''After resolving a 2-comp abstraction, the app should execute correctly.'''
        app = AbsApp2()
        fr = file_reader.FileReaderComponent('{}ft/tests/framework/TEST_INPUT.txt'.format(localhost.local_directory.package_root))
        fw = file_writer.FileWriterComponent('{}ft/tests/framework/TEST_OUTPUT.txt'.format(localhost.local_directory.package_root))
        app.add_components([fr, fw])
        
        c2_fw = connector.Connector(app._components['test_comp_out2'].get_endpoint('out'),
                                    fw.get_input_endpoint())
        fr_c1 = connector.Connector(fr.get_output_endpoint(),
                                    app._components['test_comp_out1'].get_endpoint('in'))
        app.add_connections([fr_c1, c2_fw])
        app.resolve_abstractions()
        app.create_threads()
        
        
        app.execute(True, 1)
        ret = filecmp.cmp('{}ft/tests/framework/TEST_INPUT.txt'.format(localhost.local_directory.package_root),
                          '{}ft/tests/framework/TEST_OUTPUT.txt'.format(localhost.local_directory.package_root),
                          False)
        self.assertEqual(ret, True, 'Files not equal')
        
    def test_abs_res_exec_3(self):
        '''After resolving a 2-deep abstraction, the app should execute correctly.'''
        app = AbsApp3()
        fr = file_reader.FileReaderComponent('{}ft/tests/framework/TEST_INPUT.txt'.format(localhost.local_directory.package_root))
        fw = file_writer.FileWriterComponent('{}ft/tests/framework/TEST_OUTPUT.txt'.format(localhost.local_directory.package_root))
        app.add_components([fr, fw])
        
        c2_fw = connector.Connector(app._components['test_comp_out2'].get_endpoint('out'),
                                    fw.get_input_endpoint())
        fr_c1 = connector.Connector(fr.get_output_endpoint(),
                                    app._components['test_comp_out1'].get_endpoint('in'))
        app.add_connections([fr_c1, c2_fw])
        app.resolve_abstractions()
        self.assertEqual(5, len(app._components), '{} components found, should be {}'.format(len(app._components), 5))
        self.assertEqual(4, len(app._connections), '{} connections found, should be {}'.format(len(app._connections), 4))
        app.create_threads()
        self.assertEqual(len(app._threads), 5, '{} threads found, should be {}'.format(len(app._threads), 5))

        
        app.execute(True, 1)
        
        ret = filecmp.cmp('{}ft/tests/framework/TEST_INPUT.txt'.format(localhost.local_directory.package_root),
                          '{}ft/tests/framework/TEST_OUTPUT.txt'.format(localhost.local_directory.package_root),
                          False)
        self.assertEqual(ret, True, 'Files not equal')
        
    def test_abs_res_exec_4(self):
        '''After resolving a 2-deep abstraction, the app should execute correctly.'''
        app = AbsApp3()
        fr = file_reader.FileReaderComponent('{}ft/tests/framework/TEST_INPUT.txt'.format(localhost.local_directory.package_root), 'reader')
        fw = file_writer.FileWriterComponent('{}ft/tests/framework/TEST_OUTPUT.txt'.format(localhost.local_directory.package_root))
        app.add_components([fr, fw])
        
        c2_fw = connector.Connector(app._components['test_comp_out2'].get_endpoint('out'),
                                    fw.get_input_endpoint())
        fr_c1 = connector.Connector(fr.get_output_endpoint(),
                                    app._components['test_comp_out1'].get_endpoint('in'))
        app.add_connections([fr_c1, c2_fw])
        app.resolve_abstractions()
        app.instrument('{}ft/tests/framework/inst/'.format(localhost.local_directory.package_root))
        self.assertEqual(5, len(app._components), '{} components found, should be {}'.format(len(app._components), 5))
        self.assertEqual(4, len(app._connections), '{} connections found, should be {}'.format(len(app._connections), 4))
        are_inst = True
        for conn in app._connections.values():
            are_inst = are_inst & isinstance(conn, connector.InstrumentedConnector)
        self.assertTrue(are_inst, 'All connectors were not instrumented')
        app.create_threads()
        self.assertEqual(len(app._threads), 5, '{} threads found, should be {}'.format(len(app._threads), 5))

        
        app.execute(True, 1)
        
        ret = filecmp.cmp('{}ft/tests/framework/TEST_INPUT.txt'.format(localhost.local_directory.package_root),
                          '{}ft/tests/framework/TEST_OUTPUT.txt'.format(localhost.local_directory.package_root),
                          False)
        self.assertEqual(ret, True, 'Files not equal')

                
if __name__ == "__main__":
    #import sys;sys.argv = ['', 'Test.testApplication']
    unittest.main()